{include "inc_header.html"}
<style type="text/css">
    body{
        background: #eee;
    }
    .container{
        width: 100%;
        margin: 0 auto;
        max-width: 900px;
    }
    .ui.spin-loading {
        display: inline-block;
    }
    .ui.spin-loading::before {
        content: '';
        display: inline-block;
        width: .8em;
        height: .8em;
        border-radius: 50%;
        overflow: hidden;
        border-width: 1px;
        border-style: solid;
        border-color: var(--color-base-contrast) var(--color-base-contrast) transparent transparent;
        animation: rotate360 1s infinite linear;
        transition: all .3s ease-in-out;
    }
    @keyframes rotate360 {
        100%{ transform: rotate(360deg); }
    }
    .inline-block { display: inline-block; }
    .disable-select { user-select: none; }
</style>
<div class="full-view overflow-hidden" id="page-outlet">
    <div class="container padding flex-column overflow-hidden" style="height: 100%">
        <div class="border radius light-background disable-select">
            <div class="ui flex-item padding-half">
                <div class="start padding-top-half padding-left-half">来源</div>
                <div class="center">
                    <span :class="'inline-block padding-half cursor-pointer ' + (type == 'so' ? 'medium-text' : 'primary-text')" @click="getList('gpt');"><i :class="type == 'so' ? 'fa-light fa-circle' : 'fa-solid fa-circle-check'"></i> GPT_weights</span>
                    <span :class="'inline-block padding-half cursor-pointer margin-right ' + (type == 'gpt' ? 'medium-text' : 'primary-text')" @click="getList('so');"><i :class="type == 'gpt' ? 'fa-light fa-circle' : 'fa-solid fa-circle-check'"></i> SoVITS_weights</span>
                </div>
                <div class="end"></div>
            </div>
            <div class="border-top light-border ui flex-item padding-half">
                <div class="start padding-top-half padding-left-half">标签</div>
                <div class="center">
                    <span :class="'cursor-pointer padding-half inline-block ' + (checkedTags.indexOf('未设置') >= 0 ? 'primary-text' : 'medium-text')" @click="filterTag('未设置')" v-if="">
                        <i :class="checkedTags.indexOf('未设置') >= 0 ? 'fa-solid fa-circle-check' : 'fa-light fa-circle'"></i> 未设置
                    </span>
                    <template v-if="checkedTags.indexOf('未设置') === -1">
                        <span :class="'cursor-pointer padding-half inline-block ' + (checkedTags.indexOf(tag) >= 0 ? 'primary-text' : 'medium-text')" v-for="tag in allTags" @click="filterTag(tag)">
                            <i :class="checkedTags.indexOf(tag) >= 0 ? 'fa-solid fa-circle-check' : 'fa-light fa-circle'"></i> {{ tag }}
                        </span>
                    </template>
                </div>
            </div>
        </div>
        <div class="overflow-hidden padding-top flex-column" style="flex: 1;">
            <div class="ui table scroll-enable flex-column selectable overflow-hidden">
                <div class="thead">
                    <div class="ui row">
                        <div class="col auto-width">
                            <a-input-search size="small" style="width: 200px;"
                                            v-model:value="keyword"
                                            placeholder="模型名称搜索"
                                            enter-button="搜索"
                                            allow-clear
                                            @change="onSearch"
                                            @search="onSearch"/>
                        </div>
                        <div class="col text-right" style="width: 30%">
                            <a-button size="small" type="primary" :danger="autoPlayIng" @click="autoPlay()"><i :class="'fa margin-right ' + (autoPlayIng ? 'fa-pause' : 'fa-play')"></i> {{ autoPlayIng ? '停止播放' : '开始播放' }}</a-button>
                        </div>
                    </div>
                </div>
                <div class="tbody" style="flex: 1;" ref="tBody">
                    <template v-if="loading">
                        <div class="empty-list">
                            <div class="text-center medium-text">
                                <div class="ui spin-loading" style="border-color: var(--color-medium)"></div>
                                <div class="padding-top-half">正在加载数据</div>
                            </div>
                        </div>
                    </template>
                    <template v-else>
                        <template v-if="list.length <= 0">
                            <div class="empty-list">
                                <div class="text-center medium-text">
                                    <i class="fa-solid fa-circle-info"></i>
                                    <div class="padding-top-half">没有模型数据</div>
                                </div>
                            </div>
                        </template>
                        <div class="ui row" v-for="(item, index) in list" :ref="'row_' + index">
                            <div class="col auto-width">
                                <div class="text-bold">{{ item.name }}</div>
                                <div class="text-small medium-text padding-top-half">参考：<span class="orange-text">{{ item.audioText != null && item.audioText != '' ? (item.audioText.indexOf('【默认】') === 0 ? item.audioText.replace('【默认】', '') : item.audioText) : '该模型没有参考文本' }}</span></div>
                                <div class="text-tiny red-text padding-top-half"><span class="medium-text">HASH：</span>{{ item.hash }}</div>
                                <!-- 打标签 -->
                                <div class="padding-top-half">
                                    <a-tag color="blue" class="margin-top-half" v-for="tag in (tagList[item.hash] || [])">{{ tag }}</a-tag>
                                    <a-tag color="red" class="margin-top-half cursor-pointer" @click="editTag(index, item)"><i :class="(tagList[item.hash] || []).length > 0 ? 'fa-regular fa-pen' : 'fa-regular fa-plus'"></i></a-tag>
                                </div>
                            </div>
                            <div class="col text-center" style="width: 5%;min-width: 50px;" v-if="item.audio != '' && item.audio != null && item.audio != undefined">
                                <div :class="'ui button circle tiny ' + (playIndex == index ? 'danger' : '')" @click="listen(index, item)"><i :class="'fa text-small ' + (playIndex == index ? 'fa-pause' : 'fa-play')"></i></div>
                            </div>
                            <div class="col text-center" style="width: 5%;min-width: 50px;" v-if="item.audio != '' && item.audio != null && item.audio != undefined">
                                <div class="ui button circle tiny" @click="download(item)"><i class="fa fa-download"></i> </div>
                            </div>
                        </div>
                    </template>
                </div>
                <div class="tfoot">
                    <div class="ui flex-item padding">
                        <div class="center justify-center">
                            <div class="text-small">共 <span class="red-text">{{ list.length }}</span> 个模型</div>
                        </div>
                        <div class="end">
                            <a-button size="small" @click="tBodyScrollToTop()"><i class="fa fa-arrow-up"></i></a-button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <!--  -->
    <a-modal v-model:visible="newTag.show"
             title="设置人特模型标签"
             :confirm-loading="newTag.saveLoading"
             cancel-text="取消"
             ok-text="保存"
             @ok="setTag">
        <!-- 列出可选 -->
        <div class="border radius">
            <div class="ui flex-item padding">
                <div class="start orange-text">模型名</div>
                <div class="center padding-left-half justify-center">
                    <div class="ui flex-item">
                        <div class="center text-bold">{{ newTag.name }}</div>
                        <div class="end" v-if="newTag.audio != '' && newTag.audio != null && newTag.audio != undefined">
                            <div :class="'ui button circle tiny ' + (playIndex == -2 ? 'danger' : '')" @click="listen(-2, newTag)"><i :class="'fa text-small ' + (playIndex == -2 ? 'fa-pause' : 'fa-play')"></i></div>
                        </div>
                        <div class="end" v-if="newTag.audio != '' && newTag.audio != null && newTag.audio != undefined">
                            <div class="ui button circle tiny" @click="download(newTag)"><i class="fa fa-download"></i> </div>
                        </div>
                    </div>
                    <div class="text-small medium-text">参考：<span class="orange-text">{{ newTag.audioText != null && newTag.audioText != '' ? (newTag.audioText.indexOf('【默认】') === 0 ? newTag.audioText.replace('【默认】', '') : newTag.audioText) : '该模型没有参考文本' }}</span></div>
                    <div class="text-tiny red-text"><span class="medium-text">HASH：</span>{{ newTag.hash }}</div>
                </div>
            </div>
            <div class="ui flex-item padding-half border-top light-border disable-select">
                <div class="start padding-left-half padding-top-half orange-text">选预设</div>
                <div class="center">
                    <span :class="'cursor-pointer padding-half inline-block ' + (newTag.tagList.indexOf(tag) >= 0 ? 'primary-text' : 'medium-text')" v-for="tag in allTags" @click="toggleTag(tag)">
                        <i :class="(newTag.tagList.indexOf(tag) >= 0 ? 'fa-solid fa-circle-check' : 'fa-light fa-circle')"></i> {{ tag }}
                    </span>
                </div>
            </div>
            <div class="ui flex-item padding border-top light-border">
                <div class="start justify-center orange-text">自定义</div>
                <div class="center padding-left-half"><a-input type="text" maxlength="10" placeholder="10个字以内，多个用空格分割" v-model:value="newTag.value"></a-input></div>
            </div>
        </div>
    </a-modal>
</div>
<script>
    const { createApp } = Vue;
    const { message, Modal, Empty } = antd;
    const app = createApp({
        data() {
            return {
                config: {
                    locale: antd.locales.zh_CN,
                    theme: {
                        token: {
                            borderRadius: 5
                        }
                    }
                },
                simpleImage: Empty.PRESENTED_IMAGE_SIMPLE,
                type: '{$get.type|default:"gpt"}',
                //展示的数据
                list: [],
                loading: true,
                //
                audio: null,
                playIndex: -1,
                //标签列表
                tagList: { },
                //所有标签列表
                allTags: [],
                //全部数据
                AllList: [],
                //当前筛选的标签
                checkedTags: [],
                //
                keyword: '',
                //
                newTag: {
                    show: false,
                    index: 0,
                    hash: '',
                    audio: '',
                    audioText: '',
                    name: '',
                    type: '',
                    value: '',
                    saveLoading: false,
                    tagList: []
                },
                autoPlayIng: false
            }
        },
        mounted() {
            this.initData();
        },
        methods: {
            initData() {
                //
                this.getTags();
                this.getList();
            },
            tBodyScrollToTop() {
                this.$refs.tBody.scrollTo({
                    top: 0,
                    behavior: 'smooth' // 平滑滚动效果
                });
            },
            getTags() {
                let hide = message.loading('正在获取标签...');
                axios.get("{url url='/vits/api/data/getTags'}").then(({ data }) => {
                    if (data.error === 0) {
                        this.tagList = data.data;
                        //处理
                        let tags = "男 女 儿童 少年 青年 中年 老年 性感 可爱 诱惑 成熟 古怪 聪明 愚蠢".split(" ");
                        for (let key in this.tagList) {
                            this.tagList[key].forEach(tag => {
                                if (tags.indexOf(tag.toString()) === -1) {
                                    tags.push(tag);
                                }
                            });
                        }
                        this.allTags = tags;
                    } else {
                        message.error(data.message);
                    }
                }).catch(() => {
                    message.error('服务端繁忙');
                }).finally(() => {
                    hide();
                });
            },
            getList(type) {
                if (this.audio != null) {
                    this.audio.pause();
                    this.audio = null;
                }
                type = type || this.type;
                this.type = type;
                this.playIndex = -1;
                this.autoPlayIng = false;
                this.loading = true;
                let hide = message.loading('正在获取数据...');
                axios.get("{url url='/vits/api/data/list'}", { params: { type } }).then(({ data }) => {
                    if (data.error === 0) {
                        this.tBodyScrollToTop();
                        this.AllList = data.data;
                        let keyword = this.keyword.replace(/\s+/g, '');
                        if (this.checkedTags.length > 0) {
                            let list = [];
                            data.data.forEach(item => {
                                let tags = this.tagList[item.hash] || [];
                                let bool = true;
                                if (this.checkedTags.indexOf("未设置") >= 0) {
                                    bool = tags.length === 0;
                                } else {
                                    this.checkedTags.forEach(ct => {
                                        if (tags.indexOf(ct) === -1) {
                                            bool = false;
                                        }
                                    });
                                }
                                if (bool && (item.name.indexOf(this.keyword) >= 0 || keyword.length === 0)) {
                                    list.push(item);
                                }
                            });
                            this.list = list;
                        } else if (keyword.length > 0) {
                            let list = [];
                            data.data.forEach(item => {
                                if (item.name.indexOf(this.keyword) >= 0) {
                                    list.push(item);
                                }
                            });
                            this.list = list;
                        } else {
                            this.list = data.data;
                        }
                    } else {
                        message.error(data.message);
                    }
                }).catch(() => {
                    message.error('服务端繁忙');
                }).finally(() => {
                    hide();
                    this.loading = false;
                });
            },
            listen(index, item, auto) {
                if (index == this.playIndex) {
                    return;
                }
                if (auto) {
                    let elem = this.$refs["row_" + index] || null;
                    if (elem && elem[0]) {
                        elem[0].scrollIntoView({
                            behavior: 'smooth',
                            block: 'start'
                        });
                    }
                }
                if (this.audio != null) {
                    this.audio.pause();
                    this.audio = null;
                }
                if (!item || !item.audio) {
                    if (auto) {
                        let next = this.list[index+1] || null;
                        if (next) {
                            this.listen(index+1, next, true);
                        } else {
                            if (index == this.list.length - 1) {
                                this.playIndex = -1;
                                this.autoPlayIng = false;
                            }
                        }
                    }
                    return;
                }
                this.playIndex = index;
                let url = "{url url='/vits/api/data/audio'}";
                let query = ["name=" + item.name];
                query.push("type=" + item.type);
                query.push("suffix=" + item.audio.slice(item.audio.lastIndexOf(".")+1));
                url += "?" + query.join("&");
                this.audio = new Audio(url);
                this.audio.volume = 1;
                //
                this.audio.load();
                this.audio.play();
                let self = this;
                this.audio.addEventListener('ended', function() {
                    if (auto) {
                        let next = self.list[index+1] || null;
                        if (next) {
                            self.listen(index+1, next, true);
                        } else {
                            if (index == self.list.length - 1) {
                                self.playIndex = -1;
                                self.autoPlayIng = false;
                            }
                        }
                    } else {
                        self.playIndex = -1;
                    }
                });
            },
            download(item) {
                let url = "{url url='/vits/api/data/audio'}";
                let query = ["name=" + item.name];
                query.push("type=" + item.type);
                query.push("suffix=" + item.audio.slice(item.audio.lastIndexOf(".")+1));
                url += "?" + query.join("&");
                download(item.name + item.audio.slice(item.audio.lastIndexOf(".")), url);
            },
            editTag(index, item) {
                this.newTag = {
                    show: true,
                    index: index,
                    hash: item.hash,
                    name: item.name,
                    audioText: item.audioText || null,
                    type: item.type,
                    audio: item.audio || null,
                    value: '',
                    saveLoading: false,
                    tagList: (this.tagList[item.hash] || []).concat([])
                };
            },
            toggleTag(tag) {
                if (this.newTag.tagList.indexOf(tag) >= 0) {
                    this.newTag.tagList.splice(this.newTag.tagList.indexOf(tag), 1);
                } else {
                    this.newTag.tagList.push(tag);
                }
            },
            setTag() {
                //处理自定义和预设
                let values = [];
                this.newTag.tagList.forEach(tag => {
                    tag = tag.replace(/\s+/g, '');
                    if (tag.length > 0 && values.indexOf(tag.toString()) === -1) {
                        values.push(tag);
                    }
                });
                let diy = this.newTag.value.split(" ");
                diy.forEach(tag => {
                    tag = tag.replace(/\s+/g, '');
                    if (tag.length > 0 && values.indexOf(tag.toString()) === -1) {
                        values.push(tag);
                    }
                });
                //变更
                this.newTag.saveLoading = true;
                this.tagList[this.newTag.hash] = values;
                axios.post("{url url='/vits/api/data/setTag'}", {
                    name: this.newTag.name,
                    type: this.newTag.type,
                    values: values.join(" ")
                }).then(({ data }) => {
                    if (data.error === 0) {
                        //重载
                        this.getTags();
                        //关闭
                        this.newTag.show = false;
                    } else {
                        message.error(data.message);
                    }
                }).catch(() => {
                    message.error("服务端繁忙");
                }).finally(() => {
                    this.newTag.saveLoading = false;
                });
            },
            filterTag(tag) {
                if (tag != '') {
                    if (this.checkedTags.indexOf(tag) >= 0) {
                        this.checkedTags.splice(this.checkedTags.indexOf(tag), 1);
                    } else {
                        this.checkedTags.push(tag);
                    }
                }
                let keyword = this.keyword.replace(/\s+/g, '');
                //过滤
                if (this.checkedTags.length === 0) {
                    let list = [];
                    if (keyword.length > 0) {
                        this.AllList.forEach(item => {
                            if (item.name.indexOf(this.keyword) >= 0) {
                                list.push(item);
                            }
                        });
                    } else {
                        list = this.AllList;
                    }
                    this.list = list;
                } else {
                    let list = [];
                    this.AllList.forEach(item => {
                        let tags = this.tagList[item.hash] || [];
                        let bool = true;
                        if (this.checkedTags.indexOf("未设置") >= 0) {
                            bool = tags.length === 0;
                        } else {
                            this.checkedTags.forEach(ct => {
                                if (tags.indexOf(ct) === -1) {
                                    bool = false;
                                }
                            });
                        }
                        if (bool && (item.name.indexOf(this.keyword) >= 0 || keyword.length === 0)) {
                            list.push(item);
                        }
                    });
                    this.list = list;
                }
                this.tBodyScrollToTop();
            },
            onSearch() {
                this.filterTag('');
            },
            autoPlay() {
                if (this.autoPlayIng) {
                    //暂停播放
                    if (this.audio != null) {
                        this.audio.pause();
                        this.audio = null;
                        this.playIndex = -1;
                    }
                    this.autoPlayIng = false;
                } else if (this.playIndex >= 0) {
                    //
                    message.info("正在播放，请稍后...");
                } else {
                    //从第一个开始播放
                    let item = this.list[0] || null;
                    if (item) {
                        this.autoPlayIng = true;
                        this.listen(0, item, true);
                    }
                }
            }
        }
    }).use(antd);
    //挂载
    window.vm = app.mount("#page-outlet");
    //下载文件
    async function download(name, src) {
        let audio = new Audio(src);
        audio.load();

        const response = await fetch(audio.src);
        if (!response.ok) {
            message.error('加载数据失败');
        }

        // 2. 将响应转换为Blob对象
        const blob = await response.blob();

        // 3. 创建下载链接
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');

        // 4. 设置文件名和链接
        a.href = url;
        a.download = name; // 保存的文件名

        // 5. 触发下载
        document.body.appendChild(a);
        a.click();

        // 6. 清理资源
        setTimeout(() => {
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            message.success('下载文件成功');
        }, 100);
    }
</script>
{include "inc_footer.html"}